
#include <BFont>
#include <BPainter>
#include "member_BTextEdit.h"

using namespace BWE;

TextBlock::TextBlock()
{
	dirty = true;
	line = -1;
	rowHeight = 0;
}
TextBlock::TextBlock(const TextBlock& other)
{
	dirty = other.dirty;
	line = other.line;
	rowHeight = other.rowHeight;
	font = other.font;
	rect = other.rect;
	cells = other.cells;
}
TextBlock::~TextBlock()
{

}

void TextBlock::reset(const BString& text)
{
	cells.reset();
	for (auto it = text.begin(); it < text.end(); it++)
	{
		BCode code = it.value();
		cells.append(CodeCell());
		CodeCell& cell = cells.last();
		cell.code = code;
		cell.fresh(font, rowHeight);
	}
	dirty = true;
}
void TextBlock::append(const BString& text)
{
	for (auto it = text.begin(); it < text.end(); it++)
	{
		BCode code = it.value();

		cells.append(CodeCell());
		CodeCell& cell = cells.last();
		cell.code = code;
		cell.fresh(font, rowHeight);
	}
	dirty = true;
}
void TextBlock::insert(int pos, BCode code)
{
	cells.insert(pos, CodeCell());
	CodeCell& cell = cells[pos];
	cell.code = code;
	cell.fresh(font, rowHeight);
	dirty = true;
}
void TextBlock::insert(int pos, const BString& text)
{
	int count = text.count();
	cells.insert(pos, CodeCell(), count);
	for (auto it = text.begin(); it.valid(); it++)
	{
		CodeCell& cell = cells[it.index()];
		cell.code = *it;
		cell.fresh(font, rowHeight);
	}
	dirty = true;
}

void TextBlock::refresh(BFont* font)
{
	this->font = font;
	if (font && rowHeight < 1)
		rowHeight = font->height();

	for (int i = 0; i < cells.size(); i++)
	{
		CodeCell& cell = cells[i];
		cell.fresh(font, rowHeight);
	}
	dirty = true;
}
int TextBlock::wordWidth(int index) const
{
	bool isHead = index == 0;
	if (index > 0)
	{
		isHead = cells[index - 1].isSpace() && !cells[index].isSpace();
	}
	int width = cells[index].rect.width();
	if (isHead)
	{
		while (++index < cells.size())
		{
			if (cells[index].isSpace())
				break;
			width += cells[index].rect.width();
		}
	}
	return width;
}

void TextBlock::adjust(int width)
{
	if (font)
	{
		if (rowHeight < 1)
			rowHeight = font->height();

		int fontWidth = font->width();
		int fontHeight = font->height();
		int up = fontHeight * 0.2;
		int x = 0;
		int y = 0;
		for (int i = 0; i < cells.size(); i++)
		{
			CodeCell& cell = cells[i];
			int ww = wordWidth(i);
			if(x + ww > width)
			{
				x = 0;
				y += rowHeight;
			}
			cell.rect.setPos(x, y);
			cell.image_rect.move(x + cell.image_left, cell.rect.bottom() - cell.image_top - up);
			x += cell.rect.width();
		}

		rect.setSize(width, y + rowHeight);
		dirty = false;
	}
}

int TextBlock::tail() const
{
	if (cells.empty())
		return 0;
	if (cells.last().code == '\n')
		return cells.size() - 1;
	return cells.size();
}

member_BTextEdit::member_BTextEdit(BTextEdit* textEdit)
{
	boss = textEdit;
	lineHeight = 20;
	lineShown = true;
	lineWrap = true;
	blinkTick = 0;
	blinkTime = 500;
	hoverIndex = 0;
	inputIndex = 0;
	inputShown = true;
	readOnly = false;
	changed = false;

	selected = false;
	pressed = false;
	beginIndex = 0;
	endIndex = 0;
}
member_BTextEdit::~member_BTextEdit()
{

}

void member_BTextEdit::initBlocks()
{
	BFont* font = (BFont*)boss->font();
	blocks.clear();
	int pos = 0;
	BString line;
	while(pos < text.size())
	{
		pos = text.line(line, pos, true);

		blocks.append(TextBlock());
		TextBlock& block = blocks.last();
		line.remove('\r');
		block.reset(line);
		block.refresh(font);
		block.dirty = true;
	}
	changed = false;
	inputBlock = blocks.end();
	inputIndex = 0;
	hoverBlock = blocks.end();
	hoverIndex = 0;
	beginBlock = blocks.end();
	beginIndex = 0;
	endBlock = blocks.end();
	endIndex = 0;
	updateLineNumbers();
}
void member_BTextEdit::appendBlocks(const BString& str)
{
	BFont* font = (BFont*)boss->font();

	BString line;
	int pos = 0;
	if (blocks.size() && blocks.last().cells.last() != '\n')
	{
		pos = str.line(line, pos, true);

		TextBlock& lastBlock = blocks.last();
		line.remove('\r');
		lastBlock.append(line);
		lastBlock.refresh(font);
	}

	while (pos < str.size())
	{
		pos = str.line(line, pos, true);

		blocks.append(TextBlock());
		TextBlock& block = blocks.last();
		line.remove('\r');
		block.reset(line);
		block.refresh(font);
	}

	if (!changed)
	{
		text.append(str);
	}
	inputBlock = blocks.end();
	inputIndex = 0;
	hoverBlock = blocks.end();
	hoverIndex = 0;
	updateLineNumbers();
}
void member_BTextEdit::adjustBlocks()
{
	const BFont* font = boss->font();
	if (!font)
		return;

	const BRect& crect = boss->clientRect();

	int fontHeight = font->size().height();
	BRect area = boss->area();

	showFirst = blocks.end();
	showLast = blocks.end();
	int x = area.x();
	int y = area.y();
	int height = 0;
	for (auto it = blocks.begin(); it.valid(); it++)
	{
		TextBlock& block = *it;
		if (block.dirty)
		{
			block.adjust(crect.width());
		}
		block.rect.move(x, y);
		y += block.rect.height();
		height += block.rect.height();

		if (block.rect.bottom() < crect.top() || block.rect.top() > crect.bottom())
			continue;

		if (showFirst.empty())
		{
			showFirst = it;
		}
		showLast = it;
	}
	if (showLast.valid())
		showLast += 1;
	area.setWidth(crect.width());
	area.setHeight(height);
	boss->setArea(area);
}
void member_BTextEdit::updateHover(const BPoint& pos)
{
	if (blocks.empty())
	{
		blocks.append(TextBlock());
		TextBlock& block = blocks.last();
		block.font = (BFont*)boss->font();
	}
	hoverBlock = blocks.rbegin();
	for (auto it = blocks.begin(); it.valid(); it++)
	{
		if (it->rect.bottom() > pos.y())
		{
			hoverBlock = it;
			break;
		}
	}
	hoverIndex = 0;
	BPoint point = pos - hoverBlock->rect.pos();
	while (hoverIndex < hoverBlock->cells.size())
	{
		const BRect& rect = hoverBlock->cells[hoverIndex].rect;
		if (rect.contain(point))
		{
			if (point.x() > rect.center().x())
				hoverIndex++;
			break;
		}
		hoverIndex++;
		if (hoverBlock->cells(hoverIndex).rect.top() > point.y())
		{
			break;
		}
	}
}
void member_BTextEdit::updateLineNumbers()
{
	for (auto it = blocks.begin(); it.valid(); it++)
	{
		if (it->line != it.index())
		{
			it->line = it.index();
			if (lineShown)
				it->lineText = it.index();
			else
				it->lineText.clear();
		}
	}
	const BFont* font = boss->font();
	if (font)
	{
		BString max_str(blocks.size());
		int maxWidth = bMax(font->width(), font->textSize(max_str).width());
		if (boss->perch(Part_Left) < maxWidth)
		{
			boss->setPerch(Part_Left, maxWidth);
		}
	}
}

void member_BTextEdit::selectWord()
{
	bool punct = false;
	if (inputBlock->cells[inputIndex].code < 255)
		punct = ispunct(inputBlock->cells[inputIndex].code);

	int begin = inputIndex;
	while (begin >= 0)
	{
		if (inputBlock->cells[begin].code < 255)
		{
			if (isspace(inputBlock->cells[begin].code))
				break;
			if (punct != (bool)ispunct(inputBlock->cells[begin].code))
				break;
		}
		begin--;
	}
	begin++;
	int end = begin;
	while (end < inputBlock->cells.size())
	{
		if (inputBlock->cells[end].code < 255)
		{
			if (isspace(inputBlock->cells[end].code))
				break;
			if (punct != (bool)ispunct(inputBlock->cells[end].code))
				break;
		}
		end++;
	}
	inputIndex = begin;
	beginIndex = begin;
	endIndex = end;
	selected = beginIndex < endIndex;
}
void member_BTextEdit::selectBlocks()
{
	beginBlock.reset();
	beginIndex = 0;
	endBlock.reset();
	endIndex = 0;
	if (inputBlock.valid() && hoverBlock.valid())
	{
		if (inputBlock->line <= hoverBlock->line)
		{
			beginBlock = inputBlock;
			beginIndex = inputIndex;
			endBlock = hoverBlock;
			endIndex = hoverIndex;
			selected = true;
		}
		else
		{
			beginBlock = hoverBlock;
			beginIndex = hoverIndex;
			endBlock = inputBlock;
			endIndex = inputIndex;
			selected = true;
		}
		if (inputBlock == hoverBlock)
		{
			beginBlock.reset();
			endBlock.reset();
			beginIndex = bMin(inputIndex, hoverIndex);
			endIndex = bMax(inputIndex, hoverIndex);
			if (endIndex == inputBlock->cells.size() && inputBlock->cells.last() == '\n')
				endIndex--;
			selected = beginIndex < endIndex;
		}
	}
}
void member_BTextEdit::unselected()
{
	selected = false;
	beginBlock.reset();
	beginIndex = 0;
	endBlock.reset();
	endIndex = 0;
}

bool member_BTextEdit::seekPrevWord()
{
	if (inputIndex == 0 && inputBlock.index() > 0)
	{
		inputBlock--;
		inputIndex = inputBlock->cells.size();
	}
	bool found = false;
	int begin = inputIndex;
	while (--begin > 0)
	{
		BCode code = inputBlock->cells[begin].code;
		if (code > 255 || !isspace(code))
		{
			found = true;
			continue;
		}
		if (found && isspace(code))
		{
			begin++;
			break;
		}
	}
	inputIndex = begin < 0 ? 0 : begin;
	return true;
}
bool member_BTextEdit::seekNextWord()
{
	int size = inputBlock->cells.size();
	int begin = inputIndex;
	while (begin < size)
	{
		if (inputBlock->cells[begin].code < 255)
		{
			if (isspace(inputBlock->cells[begin].code))
				break;
		}
		begin++;
	}
	while (begin < size)
	{
		if (inputBlock->cells[begin].code < 255)
		{
			if (!isspace(inputBlock->cells[begin].code))
				break;
		}
		begin++;
	}
	if (begin == size)
	{
		if (inputBlock.index() < blocks.size() - 1)
		{
			inputBlock++;
			begin = 0;
		}
	}
	inputIndex = begin;
	return true;
}
bool member_BTextEdit::seekPrev()
{
	if (inputIndex == 0)
	{
		if (inputBlock->line > 0)
		{
			inputBlock -= 1;
			inputIndex = inputBlock->tail();
		}
	}
	else
	{
		inputIndex--;
	}
	return true;
}
bool member_BTextEdit::seekNext()
{
	if (inputIndex == inputBlock->tail())
	{
		if (inputBlock->line < blocks.size() - 1)
		{
			inputBlock++;
			inputIndex = 0;
		}
	}
	else
	{
		inputIndex++;
	}
	return true;
}
bool member_BTextEdit::remove()
{
	if (selected)
	{
		if (beginBlock.valid() && endBlock.valid())
		{
			beginBlock->cells.remove(beginIndex, beginBlock->cells.size());
			endBlock->cells.remove(0, endIndex);
			beginBlock->cells.append(endBlock->cells);
			beginBlock->dirty = true;

			int count = endBlock->line - beginBlock->line - 1;
			blocks.remove(beginBlock->line + 1, endBlock->line - beginBlock->line);
			updateLineNumbers();

			inputBlock = beginBlock;
			inputIndex = beginIndex;
			beginBlock.reset();
			beginIndex = 0;
			endBlock.reset();
			endIndex = 0;

			changed = true;
		}
		else if (beginIndex < endIndex)
		{
			inputBlock->cells.remove(beginIndex, endIndex - beginIndex);
			inputBlock->dirty = true;
			if (inputBlock->cells.empty() && inputBlock->line < blocks.size() - 1)
			{
				TextBlockIterator nextBlock = inputBlock + 1;
				blocks.remove(inputBlock);
				inputBlock = nextBlock;
				inputIndex = 0;
				updateLineNumbers();
			}
			inputIndex = beginIndex;
			beginIndex = 0;
			endIndex = 0;
			changed = true;
		}
		unselected();
	}
	else
	{
		inputBlock->cells.remove(inputIndex);
		inputBlock->dirty = true;
		if (inputBlock->cells.last() != '\n' && inputBlock->line < blocks.size() - 1)
		{
			TextBlock& nextBlock = blocks[inputBlock->line + 1];
			inputBlock->cells.append(nextBlock.cells);
			blocks.remove(inputBlock->line + 1);
			updateLineNumbers();
		}
		changed = true;
	}
	inputShown = true;
	blinkTick = clock() + 500;
	return true;
}
bool member_BTextEdit::input(BCode code)
{
	if (code == '\n')
	{
		blocks.insert(inputBlock->line + 1, TextBlock());
		TextBlockIterator nextBlock = inputBlock + 1;
		nextBlock->cells.append(inputBlock->cells, inputIndex);
		nextBlock->refresh((BFont*)boss->font());
		nextBlock->dirty = true;

		inputBlock->cells.remove(inputIndex, inputBlock->cells.size());
		inputBlock->insert(inputIndex, '\n');

		updateLineNumbers();

		inputBlock = nextBlock;
		inputIndex = 0;
		changed = true;
	}
	else
	{
		inputBlock->insert(inputIndex, code);
		inputIndex++;
		inputBlock->dirty = true;
		changed = true;
	}
	inputShown = true;
	blinkTick = clock() + 500;
	return true;
}
bool member_BTextEdit::input(const BString& text)
{
	int pos = 0;
	if (text[0] == '\n')
	{
		input('\n');
		pos = 1;
	}
	while (pos < text.size())
	{
		bool linefeed = false;
		int count = 0;
		int end = pos;
		while (end < text.size())
		{
			char code = text[end];
			if (text[end] == '\n')
			{
				linefeed = true;
				end++;
				break;
			}
			count++;
			end++;
			if (code < 0)
				end++;
		}
		if (end > pos)
		{
			BString str;
			text.substr(str, pos, end);
			str.remove('\r');
			inputBlock->insert(inputIndex, str);
			if (linefeed)
			{
				blocks.insert(inputBlock->line + 1, TextBlock());
				inputBlock += 1;
				inputBlock->refresh((BFont*)boss->font());
				inputIndex = 0;
				updateLineNumbers();
			}
			else
			{
				inputIndex += count;
			}
			changed = true;
			pos = end;
		}
	}
	inputShown = true;
	blinkTick = clock() + 500;
	return true;
}

void member_BTextEdit::paintSelection(BPainter& painter)
{
	if (beginBlock.valid() && endBlock.valid())
	{
		painter.setColor(100, 136, 189);
		for (auto it = beginBlock; it <= endBlock; it++)
		{
			if (it == beginBlock)
			{
				BPoint offset = it->rect.pos();
				for (int i = 0; i < it->cells.size(); i++)
				{
					if (i >= beginIndex)
					{
						const CodeCell& cell = it->cells[i];
						painter.fillRect(cell.rect + offset);
					}
				}
				continue;
			}
			if (it == endBlock)
			{
				BPoint offset = it->rect.pos();
				for (int i = 0; i < it->cells.size(); i++)
				{
					if (i < endIndex)
					{
						const CodeCell& cell = it->cells[i];
						painter.fillRect(cell.rect + offset);
					}
				}
				continue;
			}
			BPoint offset = it->rect.pos();
			for (int ci = 0; ci < it->cells.size(); ci++)
			{
				const CodeCell& cell = it->cells[ci];
				painter.fillRect(cell.rect + offset);
			}
		}
	}
	else if (beginIndex < endIndex)
	{
		painter.setColor(100, 136, 189);
		BRange range(beginIndex, endIndex - 1);
		TextBlock& block = *inputBlock;
		BPoint offset = block.rect.pos();
		for (int bi = 0; bi < block.cells.size(); bi++)
		{
			const CodeCell& cell = block.cells[bi];
			if (range.contains(bi))
			{
				painter.fillRect(cell.rect + offset);
			}
		}
	}

}
void member_BTextEdit::paintCursor(BPainter& painter)
{
	if (!readOnly)
	{
		if (inputBlock->cells.empty())
		{
			BRect rect = inputBlock->rect;
			painter.setLineWidth(2);
			painter.drawLine(rect.leftTop(), rect.leftBottom());
		}
		else if (inputIndex < inputBlock->cells.size())
		{
			CodeCell& cell = inputBlock->cells[inputIndex];
			BRect rect = cell.rect + inputBlock->rect.pos();
			painter.setLineWidth(2);
			painter.drawLine(rect.leftTop(), rect.leftBottom());
		}
		else
		{
			CodeCell& cell = inputBlock->cells[inputIndex - 1];
			BRect rect = cell.rect + inputBlock->rect.pos();
			painter.setLineWidth(2);
			painter.drawLine(rect.rightTop(), rect.rightBottom());
		}
	}
}
